/*******************************************************************************
 * SVN­Versioning-Information:
 * ­­­­­­­­­­­­­­­­­­­­­­­­
 * $LastChangedRevision: 6 $
 * $LastChangedBy: c.a.duevel $
 * $LastChangedDate: 2009-08-26 15:12:37 +0000 (Wed, 26 Aug 2009) $
 * $HeadURL: http://robofight.googlecode.com/svn/trunk/Server/src/de/hft_stuttgart/botwar/server/Main.java $
 ******************************************************************************/
package de.hft_stuttgart.botwar.server;

import de.hft_stuttgart.botwar.server.model.Ranking;
import de.hft_stuttgart.botwar.server.helpers.IPFinder;
import de.hft_stuttgart.botwar.server.model.Game;
import de.hft_stuttgart.botwar.server.model.GameMap;
import de.hft_stuttgart.botwar.server.remote.Login;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FilenameFilter;
import java.io.IOException;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.URL;
import java.rmi.AlreadyBoundException;
import java.rmi.NotBoundException;
import java.rmi.RMISecurityManager;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import org.apache.log4j.Logger;
import org.apache.log4j.xml.DOMConfigurator;

/**
 * This is the starter class for the server.
 * @author c.a.duevel@gmail.com, pmv-mail@gmx.de, sdegiorgi@gmail.com
 */
public final class Main {

    private static Logger logger = Logger.getLogger(Main.class);
    protected static final String LOGGER_CONF_PATH =
            "de/hft_stuttgart/botwar/res/Log4JConf.xml";
    private static int registryPort = Registry.REGISTRY_PORT;
    static private Registry registry;
    private static boolean started = false;
    static String host;
    static final Timer taskTimer = new Timer();
    private static File confDir;
    private static boolean runTasks = true;
    public static final long POLL_INTERVALL = 5 * 1000;
    private static final long GAME_DELETE_INTERVALL = 60 * 1000;
    private static boolean noSecurity = false;
    private static final String SYSTEM_NAME = "server"; //used for system-msg.

    /**
     * @param args the command line arguments\n
     * -drop drop all tables from the db and create new tables
     */
    public static void main(final String[] args) {
        configureLogger();
        logger.info(String.format("Server was called with %s",
                Arrays.deepToString(args)));
        checkCmdLineParams(args);
        try {
            init();
        } catch (AlreadyBoundException ex) {
            logger.error(ex.getMessage());
            System.exit(1);
        } catch (RemoteException ex) {
            logger.error(ex.getMessage());
            System.exit(1);
        } catch (ClassNotFoundException ex) {
            logger.error(ex.getMessage());
            System.exit(1);
        } catch (SQLException ex) {
            logger.error(ex.getMessage());
            System.exit(1);
        } catch (FileNotFoundException ex) {
            logger.error(ex.getMessage());
            System.exit(1);
        } catch (IOException ex) {
            logger.error(ex.getMessage());
            System.exit(1);
        }
    }

    static void init() throws AlreadyBoundException, RemoteException,
            ClassNotFoundException, SQLException, FileNotFoundException, IOException {
        DataBase.get();
        readMaps();
        setSecuritySettings();
        createAndConfigureRegistry();
        startTasks();
        Ranking.init();
    }

    /**
     * shuts down the server
     * @throws RemoteException
     * @throws NotBoundException
     */
    public static void shutDown() throws RemoteException, NotBoundException {
        started = false;
        stopTasks();
        logger.info("Server is now shut down - Bye!");
        registry.unbind("login");
        UnicastRemoteObject.unexportObject(registry, true);
    }

    /**
     * @return <code>true</code> if the server is started
     * <code>false</code> otherwise.
     */
    public static boolean isStarted() {
        return started;
    }

    static void checkCmdLineParams(final String[] args) {
        for (int i = 0; i < args.length; i++) {
            if ("-drop".equals(args[i])) {
                DataBase.dropTable(true);
            }
            if ("-port".equals(args[i])) {
                try {
                    Integer port = Integer.valueOf(args[i + 1]);
                    if (port < 0 || port > 65535) {
                        throw new IllegalArgumentException(String.format(
                                "%d is not a valid port!", port));

                    }
                    registryPort = port;
                    i++;
                } catch (ArrayIndexOutOfBoundsException e) {
                    throw new IllegalArgumentException("Please specify " +
                            "a valid port if you use the port option.");
                } catch (NumberFormatException e) {
                    throw new IllegalArgumentException(String.format(
                            "%s is not a valid port!", args[i + 1]));
                }
            }
            if ("-host".equals(args[i])) {
                try {
                    if (IPFinder.isValidIPAddress(args[i + 1])) {
                        host = args[i + 1];
                        i++;
                    } else {
                        throw new RuntimeException(String.format("%s is not a valid IP",
                                args[i + 1]));
                    }
                } catch (ArrayIndexOutOfBoundsException e) {
                    throw new IllegalArgumentException("Please specify " +
                            "a valid ip if you use the host option.");
                }
            }
            if ("-noSecurity".equals(args[i])) {
                noSecurity = true;
            }
            if ("-confDir".equals(args[i])) {
                try {
                    i++;
                    confDir = new File(args[i]);
                    if (!confDir.isDirectory()) {
                        throw new IllegalArgumentException(
                                String.format("%s is not a valid directory." +
                                " Please specify a valid directory for the " +
                                "confDir option.", confDir));
                    }
                } catch (ArrayIndexOutOfBoundsException e) {
                    throw new IllegalArgumentException("Please specify " +
                            "a valid directory for the map option.");
                }
            }
        }
    }

    static void configureLogger() {
        URL url = ClassLoader.getSystemResource(LOGGER_CONF_PATH);
        if (url == null) {
            throw new RuntimeException(LOGGER_CONF_PATH + " wasn't found in the" + "classpath.");
        }
        DOMConfigurator.configure(url);
    }

    static void createAndConfigureRegistry() throws RemoteException, AlreadyBoundException {
        registry = LocateRegistry.createRegistry(registryPort);
        logger.info("Registry is running on port " +
                String.valueOf(registryPort));
        Login login = new Login();
        UnicastRemoteObject.exportObject(login, registryPort);
        registry.bind("login", login);
        logger.info("Login object is bound in registry");
        started = true;
        logger.info("server is ready to serve clients");
    }

    static void setSecuritySettings() {
        if (host == null) {
            try {
                List<String> ips = IPFinder.getIPs(NetworkInterface.getNetworkInterfaces());
                if (ips.size() == 1) {
                    host = ips.get(0);
                } else {
                    throw new RuntimeException(String.format("Please specify the" +
                            " host server ip with the host option!"));
                }
            } catch (SocketException e) {
                throw new RuntimeException(String.format("Please specify the" +
                        " host server ip with the host option!"));
            }
        }
        logger.debug(String.format("Setting %s as rmi server address", host));
        System.getProperties().setProperty("java.rmi.server.hostname",
                host);
        if (!noSecurity) {
            if (System.getSecurityManager() == null) {
                System.setSecurityManager(new RMISecurityManager());
            }
        }

    }

    private static void startTasks() {
        TimerTask pollTask = new TimerTask() {

            public void run() {
                if (!runTasks) {
                    taskTimer.cancel();
                }
                try {
                    Login.kickLaggingClients(POLL_INTERVALL);
                } catch (Throwable throwable) {
                    logger.error("error in pollTask", throwable);
                }
            }
        };
        TimerTask gameTask = new TimerTask() {

            public void run() {
                if (!runTasks) {
                    taskTimer.cancel();
                }
                try {
                    Game.deleteGamesWithoutPlayer();
                    Game.deleteCrashedGames();
                } catch (Throwable throwable) {
                    logger.error("error in gameTask", throwable);
                }
            }
        };
        taskTimer.scheduleAtFixedRate(pollTask, 0, POLL_INTERVALL);
        taskTimer.scheduleAtFixedRate(gameTask, 0, GAME_DELETE_INTERVALL);
    }

    public static void stopTasks() {
        runTasks = false;
        taskTimer.cancel();
    }

    static void readMaps() throws FileNotFoundException, IOException {
        if (confDir == null) {
            throw new RuntimeException("There was no mapDirectory specified!");
        }
        //get all map files
        File[] mapFiles = confDir.listFiles(new FilenameFilter() {

            public boolean accept(File dir, String name) {
                return name.toLowerCase().endsWith(".map");
            }
        });
        for (File mapFile : mapFiles) {
            GameMap map = GameMap.createMapFromFile(mapFile);
            GameMap.allMaps.put(map.name, map);
            GameMap.allMapInfos.put(map.name, map.getInfo());
        }
    }

    /**
     *
     * @return name to be used for system messages
     */
    public static String getSystemName() {
        return SYSTEM_NAME;
    }

    public static int getRegistryPort() {
        return registryPort;
    }

    private Main() {
    }

    public static File getConfDir() {
        return confDir;
    }
    
}
